/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.support.v4.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.TextView;

/**
 * PagerTitleStrip is a non-interactive indicator of the current, next, and
 * previous pages of a {@link ViewPager}. It is intended to be used as a child
 * view of a ViewPager widget in your XML layout. Add it as a child of a
 * ViewPager in your layout file and set its android:layout_gravity to TOP or
 * BOTTOM to pin it to the top or bottom of the ViewPager. The title from each
 * page is supplied by the method {@link PagerAdapter#getPageTitle(int)} in the
 * adapter supplied to the ViewPager.
 */
public class PagerTitleStrip extends ViewGroup implements ViewPager.Decor {
	// private static final String TAG = "PagerTitleStrip";

	ViewPager mPager;
	private TextView mPrevText;
	private TextView mCurrText;
	private TextView mNextText;

	private int mLastKnownCurrentPage = -1;
	private float mLastKnownPositionOffset = -1;
	private int mScaledTextSpacing;

	private boolean mUpdatingText;
	private boolean mUpdatingPositions;

	private final PageListener mPageListener = new PageListener();

	private static final int[] ATTRS = new int[] { android.R.attr.textAppearance, android.R.attr.textColor, android.R.attr.textSize };

	private static final int SIDE_ALPHA = 0x99; // single-byte alpha, 0 =
												// invisible, FF = opaque
	private static final int TEXT_SPACING = 16; // dip

	public PagerTitleStrip(Context context) {
		this(context, null);
	}

	public PagerTitleStrip(Context context, AttributeSet attrs) {
		super(context, attrs);

		addView(mPrevText = new TextView(context));
		addView(mCurrText = new TextView(context));
		addView(mNextText = new TextView(context));

		final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
		final int textAppearance = a.getResourceId(0, 0);
		if (textAppearance != 0) {
			mPrevText.setTextAppearance(context, textAppearance);
			mCurrText.setTextAppearance(context, textAppearance);
			mNextText.setTextAppearance(context, textAppearance);
		}
		if (a.hasValue(1)) {
			final int textColor = a.getColor(1, 0);
			mPrevText.setTextColor(textColor);
			mCurrText.setTextColor(textColor);
			mNextText.setTextColor(textColor);
		}
		final int textSize = a.getDimensionPixelSize(2, 0);
		if (textSize != 0) {
			mPrevText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
			mCurrText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
			mNextText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
		}
		a.recycle();

		final int defaultColor = mPrevText.getTextColors().getDefaultColor();
		final int transparentColor = (SIDE_ALPHA << 24) | (defaultColor & 0xFFFFFF);
		mPrevText.setTextColor(transparentColor);
		mNextText.setTextColor(transparentColor);

		mPrevText.setEllipsize(TruncateAt.END);
		mCurrText.setEllipsize(TruncateAt.END);
		mNextText.setEllipsize(TruncateAt.END);
		mPrevText.setSingleLine();
		mCurrText.setSingleLine();
		mNextText.setSingleLine();

		final float density = context.getResources().getDisplayMetrics().density;
		mScaledTextSpacing = (int) (TEXT_SPACING * density);
	}

	@Override
	protected void onAttachedToWindow() {
		super.onAttachedToWindow();

		final ViewParent parent = getParent();
		if (!(parent instanceof ViewPager)) {
			throw new IllegalStateException("PagerTitleStrip must be a direct child of a ViewPager.");
		}

		final ViewPager pager = (ViewPager) parent;
		final PagerAdapter adapter = pager.getAdapter();

		pager.setInternalPageChangeListener(mPageListener);
		pager.setOnAdapterChangeListener(mPageListener);
		mPager = pager;
		updateAdapter(null, adapter);
	}

	@Override
	protected void onDetachedFromWindow() {
		updateAdapter(mPager.getAdapter(), null);
		mPager.setInternalPageChangeListener(null);
		mPager.setOnAdapterChangeListener(null);
		mPager = null;
	}

	void updateText(int currentItem, PagerAdapter adapter) {
		final int itemCount = adapter != null ? adapter.getCount() : 0;
		mUpdatingText = true;

		CharSequence text = null;
		if (currentItem >= 1 && adapter != null) {
			text = adapter.getPageTitle(currentItem - 1);
		}
		mPrevText.setText(text);

		mCurrText.setText(adapter != null ? adapter.getPageTitle(currentItem) : null);

		text = null;
		if (currentItem + 1 < itemCount && adapter != null) {
			text = adapter.getPageTitle(currentItem + 1);
		}
		mNextText.setText(text);

		// Measure everything
		final int width = getWidth() - getPaddingLeft() - getPaddingRight();
		final int childHeight = getHeight() - getPaddingTop() - getPaddingBottom();
		final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (width * 0.8f), MeasureSpec.AT_MOST);
		final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
		mPrevText.measure(childWidthSpec, childHeightSpec);
		mCurrText.measure(childWidthSpec, childHeightSpec);
		mNextText.measure(childWidthSpec, childHeightSpec);

		mLastKnownCurrentPage = currentItem;

		if (!mUpdatingPositions) {
			updateTextPositions(currentItem, mLastKnownPositionOffset);
		}

		mUpdatingText = false;
	}

	@Override
	public void requestLayout() {
		if (!mUpdatingText) {
			super.requestLayout();
		}
	}

	void updateAdapter(PagerAdapter oldAdapter, PagerAdapter newAdapter) {
		if (oldAdapter != null) {
			oldAdapter.unregisterDataSetObserver(mPageListener);
		}
		if (newAdapter != null) {
			newAdapter.registerDataSetObserver(mPageListener);
		}
		if (mPager != null) {
			mLastKnownCurrentPage = -1;
			mLastKnownPositionOffset = -1;
			updateText(mPager.getCurrentItem(), newAdapter);
			requestLayout();
		}
	}

	void updateTextPositions(int position, float positionOffset) {
		if (position != mLastKnownCurrentPage) {
			updateText(position, mPager.getAdapter());
		} else if (positionOffset == mLastKnownPositionOffset) {
			return;
		}

		mUpdatingPositions = true;

		final int prevWidth = mPrevText.getMeasuredWidth();
		final int currWidth = mCurrText.getMeasuredWidth();
		final int nextWidth = mNextText.getMeasuredWidth();
		final int halfCurrWidth = currWidth / 2;

		final int stripWidth = getWidth();
		final int paddingLeft = getPaddingLeft();
		final int paddingRight = getPaddingRight();
		final int paddingTop = getPaddingTop();
		final int textPaddedLeft = paddingLeft + halfCurrWidth;
		final int textPaddedRight = paddingRight + halfCurrWidth;
		final int contentWidth = stripWidth - textPaddedLeft - textPaddedRight;

		float currOffset = positionOffset + 0.5f;
		if (currOffset > 1.f) {
			currOffset -= 1.f;
		}
		final int currCenter = stripWidth - textPaddedRight - (int) (contentWidth * currOffset);
		final int currLeft = currCenter - currWidth / 2;
		final int currRight = currLeft + currWidth;

		mCurrText.layout(currLeft, paddingTop, currRight, paddingTop + mCurrText.getMeasuredHeight());

		final int prevLeft = Math.min(paddingLeft, currLeft - mScaledTextSpacing - prevWidth);
		mPrevText.layout(prevLeft, paddingTop, prevLeft + prevWidth, paddingTop + mPrevText.getMeasuredHeight());

		final int nextLeft = Math.max(stripWidth - paddingRight - nextWidth, currRight + mScaledTextSpacing);
		mNextText.layout(nextLeft, paddingTop, nextLeft + nextWidth, paddingTop + mNextText.getMeasuredHeight());

		mLastKnownPositionOffset = positionOffset;
		mUpdatingPositions = false;
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

		if (widthMode != MeasureSpec.EXACTLY) {
			throw new IllegalStateException("Must measure with an exact width");
		}

		int childHeight = heightSize;
		int minHeight = 0;
		int padding = 0;
		final Drawable bg = getBackground();
		if (bg != null) {
			minHeight = bg.getIntrinsicHeight();
		}
		padding = getPaddingTop() + getPaddingBottom();
		childHeight -= padding;

		final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (widthSize * 0.8f), MeasureSpec.AT_MOST);
		final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, heightMode);

		mPrevText.measure(childWidthSpec, childHeightSpec);
		mCurrText.measure(childWidthSpec, childHeightSpec);
		mNextText.measure(childWidthSpec, childHeightSpec);

		if (heightMode == MeasureSpec.EXACTLY) {
			setMeasuredDimension(widthSize, heightSize);
		} else {
			int textHeight = mCurrText.getMeasuredHeight();
			setMeasuredDimension(widthSize, Math.max(minHeight, textHeight + padding));
		}
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		if (mPager != null) {
			updateTextPositions(mPager.getCurrentItem(), 0.f);
		}
	}

	private class PageListener extends DataSetObserver implements ViewPager.OnPageChangeListener, ViewPager.OnAdapterChangeListener {
		private int mScrollState;

		@Override
		public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
			if (positionOffset > 0.5f) {
				// Consider ourselves to be on the next page when we're 50% of
				// the way there.
				position++;
			}
			updateTextPositions(position, positionOffset);
		}

		@Override
		public void onPageSelected(int position) {
			if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
				// Only update the text here if we're not dragging or settling.
				updateText(mPager.getCurrentItem(), mPager.getAdapter());
			}
		}

		@Override
		public void onPageScrollStateChanged(int state) {
			mScrollState = state;
		}

		@Override
		public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter) {
			updateAdapter(oldAdapter, newAdapter);
		}

		@Override
		public void onChanged() {
			updateText(mPager.getCurrentItem(), mPager.getAdapter());
		}
	}
}
